
/**
 * libemulation
 * MOS6502 Operations
 * (C) 2010 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 *
 * Implements MOS6502 operations
 */

/*****************************************************************************
 *
 *   ops02.h
 *   Addressing mode and opcode macros for 6502,65c02,65sc02,6510,n2a03 CPUs
 *
 *   Copyright Juergen Buchmueller, all rights reserved.
 *   65sc02 core Copyright Peter Trauner, all rights reserved.
 *
 *   - This source code is released as freeware for non-commercial purposes.
 *   - You are free to use and redistribute this code in modified or
 *     unmodified form, provided you list me in the credits.
 *   - If you modify this source code, you must add a notice to each modified
 *     source file that it has been changed.  If you're a nice person, you
 *     will clearly mark each change too.  :)
 *   - If you wish to use this for commercial purposes, please contact me at
 *     pullmoll@t-online.de
 *   - The author of this copywritten work reserves the right to change the
 *     terms of its usage and license at any time, including retroactively
 *   - This entire notice must remain in the source code.
 *
 *****************************************************************************/

/* Interrupt vectors */
#define MOS6502_NMI_VECTOR	0xfffa
#define MOS6502_RST_VECTOR	0xfffc
#define MOS6502_IRQ_VECTOR	0xfffe

/* 6502 flags */
#define F_C 0x01
#define F_Z 0x02
#define F_I 0x04
#define F_D 0x08
#define F_B 0x10
#define F_T 0x20
#define F_V 0x40
#define F_N 0x80

/* some shortcuts for improved readability */
#define A	a
#define X	x
#define Y	y
#define P	p
#define S	sp.b.l
#define SPA sp.q

#define NZ	nz

#define SET_NZ(n)                                               \
    if ((n) == 0) P = (P & ~F_N) | F_Z; else P = (P & ~(F_N | F_Z)) | ((n) & F_N)

#define SET_Z(n)                                                \
    if ((n) == 0) P |= F_Z; else P &= ~F_Z

#define EAL ea.b.l
#define EAH ea.b.h
#define EAW ea.w.l
#define EAA ea.q

#define ZPL zp.b.l
#define ZPH zp.b.h
#define ZPW zp.w.l
#define ZPA zp.q

#define PCL pc.b.l
#define PCH pc.b.h
#define PCW pc.w.l
#define PCA pc.q

#define RDMEM_ID(a)		memoryBus->read(a)
#define WRMEM_ID(a,d)	memoryBus->write(a, d)

/***************************************************************
 *  RDOP    read an opcode
 ***************************************************************/
#define RDOP() memoryBus->read(PCA++); icount--

/***************************************************************
 *  RDOPARG read an opcode argument
 ***************************************************************/
#define RDOPARG() memoryBus->read(PCA++); icount--

/***************************************************************
 *  RDMEM   read memory
 ***************************************************************/
#define RDMEM(addr) memoryBus->read(addr); icount--

/***************************************************************
 *  WRMEM   write memory
 ***************************************************************/
#define WRMEM(addr,data) memoryBus->write(addr, data); icount--

/***************************************************************
 *  BRA  branch relative
 *  extra cycle if page boundary is crossed
 ***************************************************************/
#define BRA(cond)												\
    {															\
        OEUInt8 tmp2 = RDOPARG();								\
        if (cond)												\
        {														\
            RDMEM(PCW);											\
            EAW = PCW + (signed char) tmp2;						\
            if (EAH != PCH)                                     \
                RDMEM((PCH << 8 ) | EAL);						\
            PCA = EAA;											\
        }														\
    }

/***************************************************************
 *
 * Helper macros to build the effective address
 *
 ***************************************************************/

/***************************************************************
 * EA = zero page address
 ***************************************************************/
#define EA_ZPG													\
    ZPL = RDOPARG();											\
    EAA = ZPA

/***************************************************************
 * EA = zero page address + X
 ***************************************************************/
#define EA_ZPX													\
    ZPL = RDOPARG();											\
    RDMEM(ZPA);													\
    ZPL = X + ZPL;												\
    EAA = ZPA

/***************************************************************
 * EA = zero page address + Y
 ***************************************************************/
#define EA_ZPY													\
    ZPL = RDOPARG();											\
    RDMEM(ZPA);													\
    ZPL = Y + ZPL;												\
    EAA = ZPA

/***************************************************************
 * EA = absolute address
 ***************************************************************/
#define EA_ABS													\
    EAL = RDOPARG();											\
    EAH = RDOPARG()

/***************************************************************
 * EA = absolute address + X
 * one additional read if page boundary is crossed
 ***************************************************************/
#define EA_ABX_P												\
    EA_ABS; 													\
    if (EAL + X > 0xff)                                         \
        RDMEM((EAH << 8) | ((EAL + X) & 0xff));                 \
    EAW += X;

/***************************************************************
 * EA = absolute address + X
 ***************************************************************/
#define EA_ABX_NP												\
    EA_ABS;														\
    RDMEM((EAH << 8) | ((EAL + X) & 0xff));                     \
    EAW += X

/***************************************************************
 * EA = absolute address + Y
 * one additional read if page boundary is crossed
 ***************************************************************/
#define EA_ABY_P												\
    EA_ABS; 													\
    if (EAL + Y > 0xff)                                         \
        RDMEM((EAH << 8) | ((EAL + Y) & 0xff));                 \
    EAW += Y;

/***************************************************************
 * EA = absolute address + Y
 ***************************************************************/
#define EA_ABY_NP												\
    EA_ABS;														\
    RDMEM((EAH << 8) | ((EAL + Y) & 0xff));                     \
    EAW += Y

/***************************************************************
 * EA = zero page + X indirect (pre indexed)
 ***************************************************************/
#define EA_IDX													\
    ZPL = RDOPARG();											\
    RDMEM(ZPA);													\
    ZPL = ZPL + X;												\
    EAL = RDMEM(ZPA);											\
    ZPL++;														\
    EAH = RDMEM(ZPA)

/***************************************************************
 * EA = zero page indirect + Y (post indexed)
 * subtract 1 cycle if page boundary is crossed
 ***************************************************************/
#define EA_IDY_P												\
    ZPL = RDOPARG();											\
    EAL = RDMEM(ZPA);											\
    ZPL++;														\
    EAH = RDMEM(ZPA);											\
    if (EAL + Y > 0xff) 										\
        RDMEM((EAH << 8 ) | ((EAL + Y) & 0xff));                \
    EAW += Y;

/***************************************************************
 * EA = zero page indirect + Y
 ***************************************************************/
#define EA_IDY_NP												\
    ZPL = RDOPARG();											\
    EAL = RDMEM(ZPA);											\
    ZPL++;														\
    EAH = RDMEM(ZPA);											\
    RDMEM((EAH << 8) | ((EAL + Y) & 0xff));                     \
    EAW += Y

/***************************************************************
 * EA = zero page indirect (65c02 pre indexed w/o X)
 ***************************************************************/
#define EA_ZPI													\
    ZPL = RDOPARG();                                            \
    EAL = RDMEM(ZPA);                                           \
    ZPL++;                                                      \
    EAH = RDMEM(ZPA)

/***************************************************************
 * EA = indirect (only used by JMP)
 ***************************************************************/
#define EA_IND													\
    EA_ABS; 													\
    tmp = RDMEM(EAA);											\
    EAL++;	/* booby trap: stay in same page! ;-) */			\
    EAH = RDMEM(EAA);											\
    EAL = tmp

/* read a value into tmp */
/* Base number of cycles taken for each mode (including reading of opcode):
   RD_IMM               2
   RD_DUM               2
   RD_ACC               0
   RD_ZPG/WR_ZPG        3
   RD_ZPX/WR_ZPX        4
   RD_ZPY/WR_ZPY        4
   RD_ABS/WR_ABS        4
   RD_ABX_P             4/5
   RD_ABX_NP/WR_ABX_NP  5
   RD_ABY_P             4/5
   RD_ABY_NP/WR_ABY_NP  5
   RD_IDX/WR_IDX        6
   RD_IDY_P             5/6
   RD_IDY_NP/WR_IDY_NP  6
   RD_ZPI/WR_ZPI        5
 */

#define RD_IMM              tmp = RDOPARG()
#define RD_IMM_DISCARD      RDOPARG()
#define RD_DUM              RDMEM(PCW)
#define RD_ACC              tmp = A
#define RD_ZPG              EA_ZPG; tmp = RDMEM(EAA)
#define RD_ZPG_DISCARD      EA_ZPG; RDMEM(EAA)
#define RD_ZPX              EA_ZPX; tmp = RDMEM(EAA)
#define RD_ZPX_DISCARD      EA_ZPX; RDMEM(EAA)
#define RD_ZPY              EA_ZPY; tmp = RDMEM(EAA)
#define RD_ABS              EA_ABS; tmp = RDMEM(EAA)
#define RD_ABS_DISCARD      EA_ABS; RDMEM(EAA)
#define RD_ABX_P            EA_ABX_P; tmp = RDMEM(EAA)
#define RD_ABX_P_DISCARD    EA_ABX_P; RDMEM(EAA);
#define RD_ABX_NP           EA_ABX_NP; tmp = RDMEM(EAA)
#define RD_ABY_P            EA_ABY_P; tmp = RDMEM(EAA)
#define RD_ABY_NP           EA_ABY_NP; tmp = RDMEM(EAA)
#define RD_IDX              EA_IDX; tmp = RDMEM_ID(EAA); icount--
#define RD_IDY_P            EA_IDY_P; tmp = RDMEM_ID(EAA); icount--
#define RD_IDY_NP           EA_IDY_NP; tmp = RDMEM_ID(EAA); icount--
#define RD_ZPI              EA_ZPI; tmp = RDMEM(EAA)

/* write a value from tmp */
#define WR_ZPG              EA_ZPG; WRMEM(EAA, tmp)
#define WR_ZPX              EA_ZPX; WRMEM(EAA, tmp)
#define WR_ZPY              EA_ZPY; WRMEM(EAA, tmp)
#define WR_ABS              EA_ABS; WRMEM(EAA, tmp)
#define WR_ABX_NP           EA_ABX_NP; WRMEM(EAA, tmp)
#define WR_ABY_NP           EA_ABY_NP; WRMEM(EAA, tmp)
#define WR_IDX              EA_IDX; WRMEM_ID(EAA, tmp); icount--
#define WR_IDY_NP           EA_IDY_NP; WRMEM_ID(EAA, tmp); icount--
#define WR_ZPI              EA_ZPI; WRMEM(EAA, tmp)

/* dummy read from the last EA */
#define RD_EA               RDMEM(EAA)

/* write back a value from tmp to the last EA */
#define WB_ACC              A = (OEUInt8)tmp;
#define WB_EA               WRMEM(EAA, tmp)

/***************************************************************
 ***************************************************************
 *          Macros to emulate the plain 6502 opcodes
 ***************************************************************
 ***************************************************************/

/***************************************************************
 * push a register onto the stack
 ***************************************************************/
#define PUSH(Rg)            WRMEM(SPA, Rg); S--
#define PUSH_DISCARD(Rg)    RDMEM(SPA); S--

/***************************************************************
 * pull a register from the stack
 ***************************************************************/
#define PULL(Rg)            S++; Rg = RDMEM(SPA)

/* 6502 ********************************************************
 * ADC Add with carry
 ***************************************************************/
#define ADC 													\
    if (P & F_D)                                                \
    {                                                           \
        int c = (P & F_C);										\
        int lo = (A & 0x0f) + (tmp & 0x0f) + c; 				\
        int hi = (A & 0xf0) + (tmp & 0xf0); 					\
        P &= ~(F_V | F_C | F_N | F_Z);							\
        if (!((lo + hi) & 0xff))                                \
            P |= F_Z;                                           \
        if (lo > 0x09)                                          \
        {                                                       \
            hi += 0x10; 										\
            lo += 0x06; 										\
        }														\
        if (hi & 0x80)                                          \
            P |= F_N;                                           \
        if (~(A ^ tmp) & (A ^ hi) & F_N)						\
            P |= F_V;											\
        if (hi > 0x90)											\
            hi += 0x60; 										\
        if (hi & 0xff00)										\
            P |= F_C;											\
        A = (lo & 0x0f) + (hi & 0xf0);							\
    }                                                           \
    else                                                        \
    {                                                           \
        int c = (P & F_C);										\
        int sum = A + tmp + c;									\
        P &= ~(F_V | F_C);										\
        if (~(A ^ tmp) & (A ^ sum) & F_N)						\
            P |= F_V;											\
        if (sum & 0xff00)										\
            P |= F_C;											\
        A = (OEUInt8) sum;										\
        SET_NZ(A);												\
    }

/* 6502 ********************************************************
 * AND Logical and
 ***************************************************************/
#define AND 													\
    A = (OEUInt8)(A & tmp);										\
    SET_NZ(A)

/* 6502 ********************************************************
 * ASL Arithmetic shift left
 ***************************************************************/
#define ASL 													\
    P = (P & ~F_C) | ((tmp >> 7) & F_C);						\
    tmp = (OEUInt8)(tmp << 1);									\
    SET_NZ(tmp)

/* 6502 ********************************************************
 * BCC Branch if carry clear
 ***************************************************************/
#define BCC BRA(!(P & F_C))

/* 6502 ********************************************************
 * BCS Branch if carry set
 ***************************************************************/
#define BCS BRA(P & F_C)

/* 6502 ********************************************************
 * BEQ Branch if equal
 ***************************************************************/
#define BEQ BRA(P & F_Z)

/* 6502 ********************************************************
 * BIT Bit test
 ***************************************************************/
#undef BIT
#define BIT 													\
    P &= ~(F_N | F_V | F_Z);									\
    P |= tmp & (F_N | F_V);										\
    if ((tmp & A) == 0) 										\
        P |= F_Z

/* 6502 ********************************************************
 * BMI Branch if minus
 ***************************************************************/
#define BMI BRA(P & F_N)

/* 6502 ********************************************************
 * BNE Branch if not equal
 ***************************************************************/
#define BNE BRA(!(P & F_Z))

/* 6502 ********************************************************
 * BPL Branch if plus
 ***************************************************************/
#define BPL BRA(!(P & F_N))

/* 6502 ********************************************************
 * BRK Break
 * increment PC, push PC hi, PC lo, flags (with B bit set),
 * set I flag, jump via IRQ vector
 ***************************************************************/
#define BRK 													\
    RDOPARG();													\
    PUSH(PCH);													\
    PUSH(PCL);													\
    PUSH(P | F_B);												\
    P = (P | F_I);												\
    PCL = RDMEM(MOS6502_IRQ_VECTOR); 							\
    PCH = RDMEM(MOS6502_IRQ_VECTOR + 1)

/* 6502 ********************************************************
 * BVC Branch if overflow clear
 ***************************************************************/
#define BVC BRA(!(P & F_V))

/* 6502 ********************************************************
 * BVS Branch if overflow set
 ***************************************************************/
#define BVS BRA(P & F_V)

/* 6502 ********************************************************
 * CLC Clear carry flag
 ***************************************************************/
#define CLC 													\
    P &= ~F_C

/* 6502 ********************************************************
 * CLD Clear decimal flag
 ***************************************************************/
#define CLD 													\
    P &= ~F_D

/* 6502 ********************************************************
 * CLI Clear interrupt flag
 ***************************************************************/
#define CLI 													\
    P &= ~F_I

/* 6502 ********************************************************
 * CLV Clear overflow flag
 ***************************************************************/
#define CLV 													\
    P &= ~F_V

/* 6502 ********************************************************
 * CMP Compare accumulator
 ***************************************************************/
#define CMP 													\
    P &= ~F_C;													\
    if (A >= tmp)												\
        P |= F_C;												\
    SET_NZ((OEUInt8)(A - tmp))

/* 6502 ********************************************************
 * CPX Compare index X
 ***************************************************************/
#define CPX 													\
    P &= ~F_C;													\
    if (X >= tmp)												\
        P |= F_C;												\
    SET_NZ((OEUInt8)(X - tmp))

/* 6502 ********************************************************
 * CPY Compare index Y
 ***************************************************************/
#define CPY 													\
    P &= ~F_C;													\
    if (Y >= tmp)												\
        P |= F_C;												\
    SET_NZ((OEUInt8)(Y - tmp))

/* 6502 ********************************************************
 * DEC Decrement memory
 ***************************************************************/
#define DEC 													\
    tmp = (OEUInt8)(tmp - 1);									\
    SET_NZ(tmp)

/* 6502 ********************************************************
 * DEX Decrement index X
 ***************************************************************/
#define DEX 													\
    X = (OEUInt8)(X - 1);										\
    SET_NZ(X)

/* 6502 ********************************************************
 * DEY Decrement index Y
 ***************************************************************/
#define DEY 													\
    Y = (OEUInt8)(Y - 1);										\
    SET_NZ(Y)

/* 6502 ********************************************************
 * EOR Logical exclusive or
 ***************************************************************/
#define EOR 													\
    A = (OEUInt8)(A ^ tmp);										\
    SET_NZ(A)

/* 6502 ********************************************************
 * ILL Illegal opcode
 ***************************************************************/
#define ILL 													\
    logMessage("M6502 illegal opcode")

/* 6502 ********************************************************
 * INC Increment memory
 ***************************************************************/
#define INC 													\
    tmp = (OEUInt8)(tmp + 1);									\
    SET_NZ(tmp)

/* 6502 ********************************************************
 * INX Increment index X
 ***************************************************************/
#define INX 													\
    X = (OEUInt8)(X + 1);										\
    SET_NZ(X)

/* 6502 ********************************************************
 * INY Increment index Y
 ***************************************************************/
#define INY														\
    Y = (OEUInt8)(Y + 1);										\
    SET_NZ(Y)

/* 6502 ********************************************************
 * JMP Jump to address
 * set PC to the effective address
 ***************************************************************/
#define JMP                                                     \
    PCA = EAA

/* 6502 ********************************************************
 * JSR Jump to subroutine
 * decrement PC (sic!) push PC hi, push PC lo and set
 * PC to the effective address
 ***************************************************************/
#define JSR 													\
    EAL = RDOPARG();											\
    RDMEM(SPA);													\
    PUSH(PCH);													\
    PUSH(PCL);													\
    EAH = RDOPARG();											\
    PCA = EAA

/* 6502 ********************************************************
 * LDA Load accumulator
 ***************************************************************/
#define LDA 													\
    A = (OEUInt8)tmp; 											\
    SET_NZ(A)

/* 6502 ********************************************************
 * LDX Load index X
 ***************************************************************/
#define LDX 													\
    X = (OEUInt8)tmp; 											\
    SET_NZ(X)

/* 6502 ********************************************************
 * LDY Load index Y
 ***************************************************************/
#define LDY 													\
    Y = (OEUInt8)tmp; 											\
    SET_NZ(Y)

/* 6502 ********************************************************
 * LSR Logic shift right
 * 0 -> [7][6][5][4][3][2][1][0] -> C
 ***************************************************************/
#define LSR 													\
    P = (P & ~F_C) | (tmp & F_C);								\
    tmp = (OEUInt8)tmp >> 1;									\
    SET_NZ(tmp)

/* 6502 ********************************************************
 * NOP No operation
 ***************************************************************/
#define NOP

/* 6502 ********************************************************
 * ORA Logical inclusive or
 ***************************************************************/
#define ORA 													\
    A = (OEUInt8)(A | tmp);										\
    SET_NZ(A)

/* 6502 ********************************************************
 * PHA Push accumulator
 ***************************************************************/
#define PHA 													\
    PUSH(A)

/* 6502 ********************************************************
 * PHP Push processor status (flags)
 ***************************************************************/
#define PHP 													\
    PUSH(P)

/* 6502 ********************************************************
 * PLA Pull accumulator
 ***************************************************************/
#define PLA 													\
    RDMEM(SPA);													\
    PULL(A);													\
    SET_NZ(A)


/* 6502 ********************************************************
 * PLP Pull processor status (flags)
 ***************************************************************/
#define PLP 													\
    RDMEM(SPA);													\
    PULL(P);                                                    \
    P |= (F_T | F_B);

/* 6502 ********************************************************
 * ROL Rotate left
 * new C <- [7][6][5][4][3][2][1][0] <- C
 ***************************************************************/
#define ROL 													\
    tmp = (tmp << 1) | (P & F_C);								\
    P = (P & ~F_C) | ((tmp >> 8) & F_C);						\
    tmp = (OEUInt8)tmp;											\
    SET_NZ(tmp)

/* 6502 ********************************************************
 * ROR Rotate right
 * C -> [7][6][5][4][3][2][1][0] -> new C
 ***************************************************************/
#define ROR 													\
    tmp |= (P & F_C) << 8;										\
    P = (P & ~F_C) | (tmp & F_C);								\
    tmp = (OEUInt8)(tmp >> 1);									\
    SET_NZ(tmp)

/* 6502 ********************************************************
 * RTI Return from interrupt
 * pull flags, pull PC lo, pull PC hi and increment PC
 * PCW++;
 ***************************************************************/
#define RTI 													\
    RDOPARG();													\
    RDMEM(SPA);													\
    PULL(P);													\
    PULL(PCL);													\
    PULL(PCH);													\
    P |= F_T | F_B;

/* 6502 ********************************************************
 * RTS Return from subroutine
 * pull PC lo, PC hi and increment PC
 ***************************************************************/
#define RTS 													\
    RDOPARG();													\
    RDMEM(SPA);													\
    PULL(PCL);													\
    PULL(PCH);													\
    RDMEM(PCW); PCW++

/* 6502 ********************************************************
 * SBC Subtract with carry
 ***************************************************************/
#define SBC 													\
    if (P & F_D)												\
    {															\
        int c = (P & F_C) ^ F_C;								\
        int sum = A - tmp - c;									\
        int lo = (A & 0x0f) - (tmp & 0x0f) - c; 				\
        int hi = (A & 0xf0) - (tmp & 0xf0); 					\
        if (lo & 0x10)											\
        {														\
            lo -= 6;											\
            hi--;												\
        }														\
        P &= ~(F_V | F_C|F_Z|F_N);								\
        if ((A ^ tmp) & (A ^ sum) & F_N)                        \
            P |= F_V;											\
        if (hi & 0x0100)										\
            hi -= 0x60; 										\
        if ((sum & 0xff00) == 0)								\
            P |= F_C;											\
        if (!((A - tmp - c) & 0xff))							\
            P |= F_Z;											\
        if ((A - tmp - c) & 0x80)								\
            P |= F_N;											\
        A = (lo & 0x0f) | (hi & 0xf0);							\
    }															\
    else														\
    {															\
        int c = (P & F_C) ^ F_C;								\
        int sum = A - tmp - c;									\
        P &= ~(F_V | F_C);										\
        if ((A ^ tmp) & (A ^ sum) & F_N)						\
            P |= F_V;											\
        if ((sum & 0xff00) == 0)								\
            P |= F_C;											\
        A = (OEUInt8) sum;										\
        SET_NZ(A);												\
    }

/* 6502 ********************************************************
 * SEC Set carry flag
 ***************************************************************/
#if defined(SEC)
#undef SEC
#endif
#define SEC 													\
    P |= F_C

/* 6502 ********************************************************
 * SED Set decimal flag
 ***************************************************************/
#define SED 													\
    P |= F_D

/* 6502 ********************************************************
 * SEI Set interrupt flag
 ***************************************************************/
#define SEI 													\
    P |= F_I

/* 6502 ********************************************************
 * STA Store accumulator
 ***************************************************************/
#define STA 													\
    tmp = A

/* 6502 ********************************************************
 * STX Store index X
 ***************************************************************/
#define STX 													\
    tmp = X

/* 6502 ********************************************************
 * STY Store index Y
 ***************************************************************/
#define STY 													\
    tmp = Y

/* 6502 ********************************************************
 * TAX Transfer accumulator to index X
 ***************************************************************/
#define TAX 													\
    X = A;														\
    SET_NZ(X)

/* 6502 ********************************************************
 * TAY Transfer accumulator to index Y
 ***************************************************************/
#define TAY 													\
    Y = A;														\
    SET_NZ(Y)

/* 6502 ********************************************************
 * TSX Transfer stack LSB to index X
 ***************************************************************/
#define TSX 													\
    X = S;														\
    SET_NZ(X)

/* 6502 ********************************************************
 * TXA Transfer index X to accumulator
 ***************************************************************/
#define TXA 													\
    A = X;														\
    SET_NZ(A)

/* 6502 ********************************************************
 * TXS Transfer index X to stack LSB
 * no flags changed (sic!)
 ***************************************************************/
#define TXS 													\
    S = X

/* 6502 ********************************************************
 * TYA Transfer index Y to accumulator
 ***************************************************************/
#define TYA 													\
    A = Y;														\
    SET_NZ(A)
